package xin.petstore.framework.idgenerator;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Set;

public class BasicEntityIdGenerator
        implements EntityIdGenerator {
    private final long sequenceBits = 12L;
    private final long datacenterIdBits = 10L;
    private final long maxDatacenterId = 1023L;

    private final long datacenterIdShift = 12L;
    private final long timestampLeftShift = 22L;

    private final long twepoch = 1288834974657L;
    private final long datacenterId;
    private final long sequenceMax = 4096L;

    private volatile long lastTimestamp = -1L;
    private volatile long sequence = 0L;
    private static BasicEntityIdGenerator basicEntityIdGenerator;

    public static BasicEntityIdGenerator getInstance()
            throws GetHardwareIdFailedException {
        if (basicEntityIdGenerator == null) basicEntityIdGenerator = new BasicEntityIdGenerator();
        return basicEntityIdGenerator;
    }

    private BasicEntityIdGenerator() throws GetHardwareIdFailedException {
        this.datacenterId = getDatacenterId();
        if ((this.datacenterId > 1023L) || (this.datacenterId < 0L))
            throw new GetHardwareIdFailedException("datacenterId > maxDatacenterId");
    }

    public synchronized String generateLongId()
            throws InvalidSystemClockException {
        long timestamp = System.currentTimeMillis();
        if (timestamp < this.lastTimestamp) {
            throw new InvalidSystemClockException("Clock moved backwards.  Refusing to generate id for " + (
                    this.lastTimestamp - timestamp) + " milliseconds.");
        }
        if (this.lastTimestamp == timestamp) {
            this.sequence = ((this.sequence + 1L) % 4096L);
            if (this.sequence == 0L)
                timestamp = tilNextMillis(this.lastTimestamp);
        } else {
            this.sequence = 0L;
        }
        this.lastTimestamp = timestamp;
        Long id = Long.valueOf(timestamp - 1288834974657L << 22 |
                this.datacenterId << 12 |
                this.sequence);
        return id.toString();
    }

    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    protected long getDatacenterId() throws GetHardwareIdFailedException {
        try {
            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            long id;
            if (network == null) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                id = (0xFF & mac[(mac.length - 1)] | 0xFF00 & mac[(mac.length - 2)] << 8) >> 6;
            }
            return id;
        } catch (SocketException e) {
            throw new GetHardwareIdFailedException(e);
        } catch (UnknownHostException e) {
            throw new GetHardwareIdFailedException(e);
        }

    }

    public static void main(String[] args) {
        Set ids = new HashSet();
        for (int i = 0; i < 100000; i++) {
            try {
                String result = getInstance().generateLongId();
                System.out.println(result);
            } catch (InvalidSystemClockException e) {
                e.printStackTrace();
            } catch (GetHardwareIdFailedException e) {
                e.printStackTrace();
            }
        }
    }
}
